package com.hzgj.bcl.soa;

import com.hzgj.bcl.soa.rpc.message.Message;
import com.hzgj.bcl.soa.util.Util;
import com.hzgj.bcl.util.jmx.annotation.ManagedAttribute;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadFactory;

public abstract class Interceptor {

    @ManagedAttribute(description = "Give the protocol a different ID if needed so we can have multiple " +
            "instances of it in the same chainStack", writable = false)
    protected final Logger log = LoggerFactory.getLogger(this.getClass());
    protected Interceptor up_int, down_int;
    protected ChainStack chain;
    @ManagedAttribute(description = "Determines whether to collect statistics (and expose them via JMX). Default is true", writable = true)
    protected boolean stats = true;
    @ManagedAttribute(description = "Enables ergonomics: dynamically find the best values for properties at runtime")
    protected boolean ergonomics = true;
    /**
     * The name of the protocol. Is by default set to the protocol's classname. This property should rarely need to
     * be set, e.g. only in cases where we want to create more than 1 protocol of the same class in the same chainStack
     */
    @ManagedAttribute(name = "name", description = "Give the protocol a different name if needed so we can have multiple " +
            "instances of it in the same chainStack (also change ID)", writable = false)
    protected String name = getClass().getSimpleName();

    public boolean isErgonomics() {
        return ergonomics;
    }

    public void setErgonomics(boolean ergonomics) {
        this.ergonomics = ergonomics;
    }

    public ChainStack getChain() {
        return chain;
    }

    public void setChain(ChainStack s) {
        this.chain = s;
    }

    public boolean statsEnabled() {
        return stats;
    }

    public void enableStats(boolean flag) {
        stats = flag;
    }

    public String getName() {
        return name;
    }

    public Interceptor getUpInterceptor() {
        return up_int;
    }

    public void setUpInterceptor(Interceptor prot) {
        this.up_int = prot;
    }

    public Interceptor getDownInterceptor() {
        return down_int;
    }

    public void setDownInterceptor(Interceptor prot) {
        this.down_int = prot;
    }

    public Object getValue(String name) {
        if (name == null) {
            return null;
        }
        Field field = Util.getField(getClass(), name);
        if (field == null) {
            throw new IllegalArgumentException("field \"" + name + "\n not found");
        }
        return Util.getField(field, this);
    }

    public Interceptor setValues(Map<String, Object> values) {
        if (values == null) {
            return this;
        }
        for (Map.Entry<String, Object> entry : values.entrySet()) {
            String attrname = entry.getKey();
            Object value = entry.getValue();
            Field field = Util.getField(getClass(), attrname);
            if (field != null) {
                Util.setField(field, this, value);
            }
        }
        return this;
    }


    /**
     * After configuring the protocol itself from the properties defined in the XML config, a protocol might have
     * additional objects which need to be configured. This callback allows a protocol developer to configure those
     * other objects. This call is guaranteed to be invoked <em>after</em> the protocol itself has
     * been configured. See AUTH for an example.
     *
     * @return
     */
    protected List<Object> getConfigurableObjects() {
        return null;
    }

    /**
     * Supposed to be overwritten by subclasses. Usually the transport returns a valid non-null thread factory, but
     * thread factories can also be created by individual protocols
     *
     * @return
     */
    public ThreadFactory getThreadFactory() {
        return down_int != null ? down_int.getThreadFactory() : null;
    }

    public void start() throws Exception {
    }

    public void stop() {
    }

    public void destroy() {
    }

    /**
     * List of events that are required to be answered by some layer above
     */
    public List<Integer> requiredUpServices() {
        return null;
    }

    /**
     * List of events that are required to be answered by some layer below
     */
    public List<Integer> requiredDownServices() {
        return null;
    }

    /**
     * List of events that are provided to layers above (they will be handled when sent down from above)
     */
    public List<Integer> providedUpServices() {
        return null;
    }

    /**
     * List of events that are provided to layers below (they will be handled when sent down below)
     */
    public List<Integer> providedDownServices() {
        return null;
    }

    /**
     * Returns all services provided by protocols below the current protocol
     */
    public final List<Integer> getDownServices() {
        List<Integer> retval = new ArrayList<>();
        Interceptor prot = down_int;
        while (prot != null) {
            List<Integer> tmp = prot.providedUpServices();
            if (tmp != null && !tmp.isEmpty()) {
                retval.addAll(tmp);
            }
            prot = prot.down_int;
        }
        return retval;
    }

    /**
     * Returns all services provided by the protocols above the current protocol
     */
    public final List<Integer> getUpServices() {
        List<Integer> retval = new ArrayList<>();
        Interceptor prot = up_int;
        while (prot != null) {
            List<Integer> tmp = prot.providedDownServices();
            if (tmp != null && !tmp.isEmpty()) {
                retval.addAll(tmp);
            }
            prot = prot.up_int;
        }
        return retval;
    }


    /**
     * An event was received from the layer below. Usually the current layer will want to examine
     * the event type and - depending on its type - perform some computation
     * (e.g. removing headers from a MSG event type, or updating the internal membership list
     * when receiving a VIEW_CHANGE event).
     * Finally the event is either a) discarded, or b) an event is sent down
     * the chainStack using <code>down_int.down()</code> or c) the event (or another event) is sent up
     * the chainStack using <code>up_int.up()</code>.
     */
    public Object up(Event evt) {
        return up_int.up(evt);
    }

    protected boolean accept(Message msg) {
        return msg.getHeader(getName()) != null;
    }


    /**
     * An event is to be sent down the chainStack. The layer may want to examine its type and perform
     * some action on it, depending on the event's type. If the event is a message MSG, then
     * the layer may need to add a header to it (or do nothing at all) before sending it down
     * the chainStack using <code>down_int.down()</code>. In case of a GET_ADDRESS event (which tries to
     * retrieve the chainStack's address from one of the bottom layers), the layer may need to handle
     * a new response event back up the chainStack using <code>up_int.up()</code>.
     */
    public Object down(Event evt) {
        return down_int.down(evt);
    }


    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sharable {
        // no value
    }
}
